// sdlayout.cpp
//

#include "sdlayout.hpp"



// SDLayout::SDLayout
//
SDLayout::SDLayout ()
{
  // builtin types
  register_builtin_type
    ({
      "bool",
      GL_BOOL,
      4,
      4,
    });
  register_builtin_type
    ({
      "int",
      GL_INT,
      4,
      4,
    });
  register_builtin_type
    ({
      "float",
      GL_FLOAT,
      4,
      4,
    });
  register_builtin_type
    ({
      "vec3",
      GL_FLOAT_VEC3,
      12,
      16,
    });
  register_builtin_type
    ({
      "vec4",
      GL_FLOAT_VEC4,
      16,
      16,
    });
  register_builtin_type
    ({
      "mat3",
      GL_FLOAT_MAT3,
      48, // stored as 3x4
      16, // [fixme] not sure
    });
  register_builtin_type
    ({
      "mat4",
      GL_FLOAT_MAT4,
      64,
      16, // [fixme] not sure
    });
}



// SDLayout::register_type
//
SDType &SDLayout::register_type ( std::string name,
                                  GLenum gl_type,
                                  size_t size,
                                  size_t alignment,
                                  std::vector<SDTypeMember> members )
{
  auto r = types.try_emplace(name, name, gl_type, size, alignment, members);
  if (!r.second)
    GLT_FATAL("type duplicated: '%s'", name.c_str());
  return r.first->second;
}



// SDLayout::register_builtin_type
//
const SDType &SDLayout::register_builtin_type ( SDBuiltinTypeInfo info )
{
  return register_type(info.name, info.gl_type, info.size, info.alignment, {});
}



// SDLayout::register_struct_type
//
const SDType &SDLayout::register_struct_type ( SDStructTypeInfo info )
{
  std::vector<SDTypeMember> members;
  size_t offset = 0;
  for (auto m: info.members)
    {
      const SDType &mtype = get_type(m.type);
      offset = pad_size(offset, mtype.alignment);
      GLT_ASSERT(m.array_size >= 1);
      members.emplace_back(m.name, mtype, offset, m.array_size);
      // [fixme] is the last element padded in an array?
      offset += pad_array_size(mtype.size, mtype.alignment, m.array_size);
    }

  // note: alignment = vec4 alignment
  //
  // [fixme] gl_type?
  return register_type(info.name, GL_NONE, offset, 16, members);
}



// SDLayout::register_uniform_block
//
void SDLayout::register_uniform_block ( SDUniformBlockInfo info )
{
  register_struct_type
    ({
      info.name,
      info.members,
    });
  SDType &type = _get_type(info.name);
  
  auto r = uniform_blocks.try_emplace(info.name, info.name, type);
  GLT_ASSERT(r.second);

  SDUniformBlock &block = r.first->second;

  create_uniform_block_components(block);
}



// SDLayout::register_component
//
SDUniformComponent &SDLayout::register_component ( SDUniformBlock &block,
                                                   const std::string &name,
                                                   const SDType &type,
                                                   size_t offset,
                                                   size_t size )
{
  auto r = uniform_components.try_emplace(name, block, name, type, offset, size);
  if (!r.second)
    GLT_FATAL("component name duplicated: '%s'", name.c_str());
  block.components.push_back(&r.first->second);
  return r.first->second;
}



// SDLayout::create_uniform_block_components
//
void SDLayout::create_uniform_block_components ( SDUniformBlock &block )
{
  init_member_components(block, "", block.self, 0);
}



// SDLayout::init_member_components
//
size_t SDLayout::init_member_components ( SDUniformBlock &block,
                                          const std::string &base_name,
                                          SDTypeMember &member,
                                          size_t offset )
{
  // GLT_TRACE("init_member_component(%s, %s, %lu) -> type=%s, size=%lu, align=%lu, array_size=%lu",
  //           base_name.c_str(),
  //           member.name.c_str(),
  //           offset,
  //           member.type.name.c_str(),
  //           member.type.size,
  //           member.type.alignment,
  //           member.array_size);

  
  std::string full_name = (base_name == "") ? member.name : (base_name + "." + member.name);
  offset = pad_size(offset, member.type.alignment);
  size_t full_size = pad_array_size(member.type.size, member.type.alignment, member.array_size);
  register_component(block, full_name, member.type, offset, full_size);

  for (size_t i = 0; i < member.array_size; i++)
    {
      std::string elem_name = full_name + "[" + std::to_string(i) + "]";
      offset = pad_size(offset, member.type.alignment);

      if (member.array_size > 1)
        register_component(block, elem_name, member.type, offset, member.type.size);
      
      for (auto child: member.type.members)
        {
          offset = pad_size(offset, child.type.alignment);
          init_member_components(block,
                                 ((member.array_size > 1) ? elem_name : full_name),
                                 child,
                                 offset);
          offset += child.type.size;
        }

      // offset += member.type.size;
    }
  
  return offset;
}



// SDLayout::get_type
//
const SDType &SDLayout::get_type ( const std::string &name )
{
  return _get_type(name);
}



// SDLayout::_get_type
//
SDType &SDLayout::_get_type ( const std::string &name )
{
  auto it = types.find(name);
  if (it == types.end())
    GLT_FATAL("unknown type: '%s'", name.c_str());
  return it->second;
}



// SDLayout::get_uniform_block
//
const SDUniformBlock &SDLayout::get_uniform_block ( const std::string &name )
{
  auto it = uniform_blocks.find(name);
  GLT_ASSERT(it != uniform_blocks.end());
  return it->second;
}



// SDLayout::lookup_uniform_component
//
const SDUniformComponent *SDLayout::lookup_uniform_component ( const std::string &name )
{
  auto it = uniform_components.find(name);
  if (it == uniform_components.end())
    return nullptr;
  else
    return &it->second;
}



// SDLayout::dump_uniform_block
//
void SDLayout::dump_uniform_block ( const std::string &name )
{
  const SDUniformBlock &block = get_uniform_block(name);
  GLT_TRACE("**** uniform block: %s ****", block.get_name().c_str());
  size_t comp_index = 0;
  for (auto comp: block.components)
    {
      GLT_TRACE("  [%3lu] %-30s (offset=%lu, size=%lu)",
                comp_index,
                comp->full_name.c_str(),
                comp->offset,
                comp->size);
      comp_index++;
    }
}
